/**
 * Copyright (c) 2018, dreamkaylee@foxmail.com All Rights Reserved.
 */
  
package configuration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.SessionValidationScheduler;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import com.common.shiro.cache.ShiroSpringCacheManager;
import com.common.shiro.filter.CustomFormAuthenticationFilter;
import com.common.shiro.filter.KickoutSessionControlFilter;
import com.common.shiro.filter.RoleAuthorizationFilter;
import com.common.shiro.filter.SessionAdviceFilter;
import com.common.shiro.listener.OnlineListener;
import com.common.shiro.util.RetryLimitCredentialsMatcher;
import com.common.shiro.util.ShiroCacheUtil;
import com.limk.jconfig.realm.PpmsRealm;

/**
 * shiro配置
 * @author limk
 * @date 2018年2月11日 上午11:13:05
 * @version 1.0
 */
@Configuration
public class ShiroConfig extends CacheConfig {
	
	private static final Logger LOGGER = LogManager.getLogger(ShiroConfig.class);
	
	/**
	 * shiro缓存管理器
	 * @return
	 */
    @Bean(name = "cacheManager")
	public CacheManager cacheManager() {
    	LOGGER.info("----------Shiro CacheManager----------");
    	ShiroSpringCacheManager shiroSpringCacheManager = new ShiroSpringCacheManager();
    	shiroSpringCacheManager.setCacheManager(springCacheManager());
		return shiroSpringCacheManager;
	}
    
	/**
	 * 凭证匹配器
	 * @return
	 */
	@Bean(name = "credentialsMatcher")
	public CredentialsMatcher credentialsMatcher() {
		LOGGER.info("----------凭证匹配器----------");
		RetryLimitCredentialsMatcher credentialsMatcher = new RetryLimitCredentialsMatcher(cacheManager());
		// 密码错误5次锁定10分钟
		credentialsMatcher.setRetryLimitCacheName("passwordRetryCache");
		// 密码加密 1次md5,增强密码可修改此处
		credentialsMatcher.setHashAlgorithmName("md5");
		credentialsMatcher.setHashIterations(1);
		credentialsMatcher.setStoredCredentialsHexEncoded(true);
		return credentialsMatcher;
	}
    
	/**
	 * 自定义Realm
	 * @return
	 */
	@Bean(name = "ppmsRealm")
	@DependsOn("lifecycleBeanPostProcessor")
	public Realm ppmsRealm() {
		LOGGER.info("----------自定义Realm----------");
		PpmsRealm realm = new PpmsRealm(cacheManager(), credentialsMatcher());
		realm.setCachingEnabled(false);
		return realm;
	}
	
	/**
	 * 会话ID生成器
	 * @return
	 */
	@Bean
	public JavaUuidSessionIdGenerator sessionIdGenerator() {
		return new JavaUuidSessionIdGenerator();
	}
	
	/**
	 * 会话Cookie模板
	 * @return
	 */
	@Bean(name = "sessionIdCookie")
	public Cookie sessionIdCookie() {
		LOGGER.info("----------会话Cookie模板----------");
		SimpleCookie simpleCookie = new SimpleCookie("sid");
		simpleCookie.setMaxAge(-1);
		simpleCookie.setHttpOnly(true);
		return simpleCookie;
	}
	
	/**
	 * rememberMe Cookie
	 * @return
	 */
	@Bean(name = "rememberMeCookie")
	public Cookie rememberMeCookie() {
		LOGGER.info("----------rememberMe Cookie----------");
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		// 7天
		simpleCookie.setMaxAge(7 * 24 * 60 * 60);
		simpleCookie.setHttpOnly(true);
		return simpleCookie;
	}
	
	/**
	 * rememberMe管理器
	 * @return
	 */
	@Bean(name = "rememberMeManager")
	public RememberMeManager rememberMeManager() {
		LOGGER.info("----------rememberMe管理器----------");
	    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
	    cookieRememberMeManager.setCipherKey(Base64.decode("sA1FGmFsXUHwPXrJOyWYfA=="));
	    cookieRememberMeManager.setCookie(rememberMeCookie());
	    return cookieRememberMeManager;
	}
	
	/**
	 * 会话DAO
	 * @return
	 */
	@Bean(name = "sessionDAO")
	public SessionDAO sessionDAO() {
		LOGGER.info("----------会话DAO----------");
		EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
		sessionDAO.setActiveSessionsCacheName("activeSessionCache");
		sessionDAO.setSessionIdGenerator(sessionIdGenerator());
		return sessionDAO;
	}
	
	/**
	 * 会话验证调度器
	 * @return
	 */
	@Bean
	public SessionValidationScheduler sessionValidationScheduler() {
		LOGGER.info("----------会话验证调度器----------");
		ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
		scheduler.setInterval(1800000L);
		return scheduler;
	}
	
	/**
	 * 会话管理器
	 * @return
	 */
	@Bean(name = "sessionManager")
	public SessionManager sessionManager() {
		LOGGER.info("----------会话管理器----------");
		DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
		// 设置全局会话超时时间 半小时
		defaultWebSessionManager.setGlobalSessionTimeout(30 * 60 * 1000);
		defaultWebSessionManager.setDeleteInvalidSessions(true);
		defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
		defaultWebSessionManager.setSessionValidationScheduler(sessionValidationScheduler());
		// url上带sessionId 默认为true
		defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);
		defaultWebSessionManager.setSessionDAO(sessionDAO());
		defaultWebSessionManager.setSessionIdCookieEnabled(true);
		defaultWebSessionManager.setSessionIdCookie(sessionIdCookie());
		Collection<SessionListener> listeners = new ArrayList<>();
		listeners.add(new OnlineListener());
		defaultWebSessionManager.setSessionListeners(listeners);
		return defaultWebSessionManager;
	}
	
	/**
	 * 安全管理器
	 * @return
	 */
	@Bean(name = "securityManager")
	public DefaultWebSecurityManager securityManager() {
		LOGGER.info("----------安全管理器----------");
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(ppmsRealm());
		securityManager.setSessionManager(sessionManager());
		securityManager.setCacheManager(cacheManager());
		securityManager.setRememberMeManager(rememberMeManager());
		return securityManager;
	}
	
	/**
	 * Shiro过滤器
	 * @return
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter() {
		LOGGER.info("----------Shiro过滤器----------");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager());
		shiroFilterFactoryBean.setLoginUrl("/login");
		shiroFilterFactoryBean.setSuccessUrl("/index");
		shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
		
		Map<String, Filter> filtersMap = new HashMap<>(16);
	    filtersMap.put("kickout", kickoutSessionControlFilter());
	    filtersMap.put("session", new SessionAdviceFilter());
	    filtersMap.put("role", new RoleAuthorizationFilter());
	    filtersMap.put("authc", formAuthenticationFilter());
	    shiroFilterFactoryBean.setFilters(filtersMap);
	    
	    Map<String, String> chainMap = new LinkedHashMap<>();
	    chainMap.put("/captcha-image","anon");
	    chainMap.put("/build/**","anon");
	    chainMap.put("/plugins/**","anon");
	    chainMap.put("/images/**","anon");
	    chainMap.put("/login","anon");
	    chainMap.put("/druid/*","anon");
	    chainMap.put("/404","anon");
	    chainMap.put("/500","anon");
	    chainMap.put("/logout","kickout, user");
	    chainMap.put("/index","kickout, user");
	    chainMap.put("/**","authc");
	    shiroFilterFactoryBean.setFilterChainDefinitionMap(chainMap);
		return shiroFilterFactoryBean;
	}
	
	/**
	 * 自定义form认证过虑器
	 * @return
	 */
	@Bean
	public FormAuthenticationFilter formAuthenticationFilter() {
		return new CustomFormAuthenticationFilter();
	}
	
	/**
	 * 配置并发登录人数控制 同一账号只能登录一次
	 * @return
	 */
	@Bean
	public AccessControlFilter kickoutSessionControlFilter() {
		LOGGER.info("----------并发登录过滤器----------");
		KickoutSessionControlFilter filter = new KickoutSessionControlFilter();
		filter.setCacheManager(cacheManager());
		filter.setSessionManager(sessionManager());
		filter.setKickoutAfter(false);
		filter.setMaxSession(1);
		filter.setKickoutUrl("/login?kickout=1");
		return filter;
	}
	
	/**
	 * 相当于调用SecurityUtils.setSecurityManager(securityManager)
	 * @return
	 */
	@Bean
	public MethodInvokingFactoryBean methodInvokingFactoryBean() {
		LOGGER.info("----------SecurityUtils.setSecurityManager(securityManager)----------");
		MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
		factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
		Object[] obj = {securityManager()};
		factoryBean.setArguments(obj);
		return factoryBean;
	}
	
	/**
	 * shiro工具类注入cacheManager
	 * @return
	 */
	@Bean
	public ShiroCacheUtil shiroCacheUtil() {
		LOGGER.info("----------shiro缓存操作工具----------");
		ShiroCacheUtil cacheUtil = new ShiroCacheUtil();
		cacheUtil.setCacheManager(cacheManager());
		return cacheUtil;
	}
	
	/**
	 * Shiro生命周期
	 * @return
	 */
	@Bean(name = "lifecycleBeanPostProcessor")
	public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
	    return new LifecycleBeanPostProcessor();
	}
	
}
  